/**
 * File: GatherStatisticFasta.java
 * Created by: mhaimel
 * Created on: 30 Jun 2009
 * CVS:  $Id: GatherCustomStatistics.java,v 1.2 2009/12/07 09:45:00 mhaimel Exp $
 */
package uk.ac.ebi.curtain.exec;

import java.io.Closeable;
import java.io.File;
import java.io.IOException;
import java.io.PrintStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.Map.Entry;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.commons.lang.StringUtils;

import uk.ac.ebi.curtain.utils.CurtainUncheckedException;
import uk.ac.ebi.curtain.utils.StatisticHelper;
import uk.ac.ebi.curtain.utils.data.FileType;
import uk.ac.ebi.curtain.utils.data.ReadType;
import uk.ac.ebi.curtain.utils.file.EntryMapper;
import uk.ac.ebi.curtain.utils.file.EntryMapperBean;
import uk.ac.ebi.curtain.utils.file.FileInfo;
import uk.ac.ebi.curtain.utils.file.ParseUtil;
import uk.ac.ebi.curtain.utils.io.impl.FileIO;
import uk.ac.ebi.velvet.processor.StatisticsInfo;
import uk.ac.ebi.velvet.processor.StatisticsProcessor;
import uk.ac.ebi.velvet.processor.impl.SequenceStatisticsProcessor;

/**
 * @author mhaimel
 * 
 */
public class GatherCustomStatistics implements Closeable {
	
//	N60, N75, N80 and N90
	
	private FileType ftype = FileType.fasta;
	private volatile String sep = "\t";
	private final AtomicInteger limit = new AtomicInteger(100);
	private final AtomicBoolean ignoreNs = new AtomicBoolean(false);
	private final AtomicBoolean calcNs = new AtomicBoolean(false);
	private final AtomicBoolean verbose = new AtomicBoolean(false);
	private final AtomicLong maxTotal = new AtomicLong(0);
	private final List<File> fileList = new LinkedList<File>();
	private volatile PrintStream output = null;
	private volatile File outFile = null;
	
	public GatherCustomStatistics() {
		// nothing
	}

	public static void main(String[] args) {	
		GatherCustomStatistics stats = new GatherCustomStatistics();
		int i = 0;
		while (i < args.length && args[i].startsWith("-")) {
			if (args[i].equals("-h") || args[i].equals("-help")
					|| args[i].equals("--help")) {
				System.out.println("-l\tSequence cut-off length, default:100");
				System.out.println("-t\tSequence type, default:fasta");
				System.out.println("-x\tIgnore Ns, default:false");
				System.out.println("-o\tOutput file, default:stdout");
				System.out.println("-m\tMaximum total length considered (top hits), default:0");
				System.out.println("-N\tN N50 default:false");
				System.out.println("-V\tverbose default:false");
				++i;
				System.exit(1);
			} else if (args[i].equals("-l")) {
				++i;
				stats.limit.set(Integer.valueOf(args[i++]));
			} else if (args[i].equals("-t")) {
				++i;
				FileType value = FileType.valueOf(args[i++]);
				stats.ftype = value;
			} else if (args[i].equals("-o")) {
				stats.outFile = new File(args[++i]);
				++i;
			}else if (args[i].equals("-x")) {
				++i;
				Boolean val = Boolean.valueOf(args[i++]);
				stats.ignoreNs.set(val);
			}else if (args[i].equals("-m")) {
				++i;
				Long val = Long.valueOf(args[i++]);
				stats.maxTotal.set(val);
			}else if (args[i].equals("-N")) {
				++i;
				Boolean val = Boolean.valueOf(args[i++]);
				stats.calcNs.set(val);
			}else if (args[i].equals("-V")) {
				++i;
				Boolean val = Boolean.valueOf(args[i++]);
				stats.verbose.set(val);
			}
		}
		while(i < args.length){
			stats.fileList.add(new File(args[i++]));
		}
		try{
			stats.process();
		} finally{
			FileIO.closeQuietly(stats);
		}		
	}

	private void process() {
		printHeader();
		for(File in : fileList){
			FileInfo fi = new FileInfo(in, ftype, ReadType.ShortPaired);
			process(fi);
		}
	}
	
	
	private final Pattern findNs = Pattern.compile("n+", Pattern.CASE_INSENSITIVE);
	private void process(FileInfo fi) {
		final MyProcessor statProc = new MyProcessor(fi);
		final AtomicLong cnt = new AtomicLong(0);
		final AtomicInteger plainCnt = new AtomicInteger(0);
		final AtomicLong plainSum = new AtomicLong(0);
		ParseUtil seqParser = fi.getFileType().getParser();
		seqParser.parse(fi, new EntryMapper() {
			@Override
			public void mapEntry(EntryMapperBean bean) {
				int seqLen = bean.getSequenceLength();
				if(calcNs.get()){
					
					Matcher m = findNs.matcher(bean.getSequence());
					while(m.find()){
						String g = m.group();
						EntryMapperBean e = new EntryMapperBean(-1, bean.getInfo(), bean.getId(), null, g);
						verbose(e);
						statProc.addEntry(e);
					}
				} else if (seqLen >= limit.get()) {
					String seq = bean.getSequence();
					long len = seq.length();
					String seqNoNs = seq.replace("N", "").replace("n", "");
					long len2 = seqNoNs.length();
					long delta = (long) len - len2;
					if (delta == 0) {
						plainCnt.incrementAndGet();
						plainSum.addAndGet(len);
					}
					cnt.addAndGet(delta);
					if(ignoreNs.get() && delta > 0){
						bean.setSequence(seqNoNs);
						bean.setSequenceLength(seqNoNs.length());
					} 
					statProc.addEntry(bean);
				}
			}
			private void verbose(EntryMapperBean e) {
				if(verbose.get()){
					getOutput().print("-v");
					getOutput().print(sep);
					getOutput().print(e.getSequenceLength());
					getOutput().print(sep);
					getOutput().println(e.getId());
				}
			}
		});
		
		StatisticsInfo stats = 
			this.maxTotal.get() > 0 ?
					statProc.processStatistics(this.maxTotal.get()):
					statProc.processStatistics();
		List<Integer> lenArr = getSortedLengthArr(statProc);
		if(null == lenArr){
			lenArr = new ArrayList<Integer>();
		}
		StatisticHelper<Integer> sh = new StatisticHelper<Integer>(lenArr);
		Integer val = sh.getN50();

		PrintStream out = getOutput();
		if(val == null){
			out.print(StringUtils.repeat("-"+sep, 14));
			out.print(lenArr.size());
			out.print(sep);
			out.print(StringUtils.repeat("-"+sep, 4));
			out.print(fi.getFile());
			out.println(sep);
			return;
		}
		Integer n50 = null;
		if(null != val){
			n50 = val.intValue();
		} 
		
		int i = lenArr.size();
		int nCnt = 0;
		while((i > 0) && lenArr.get(--i) >= n50){
			++nCnt;
		}

		out.print(StatisticHelper.getNxValue(lenArr, 90).intValue());
		out.print(sep);
		out.print(StatisticHelper.getNxValue(lenArr, 80).intValue());
		out.print(sep);
		out.print(StatisticHelper.getNxValue(lenArr, 75).intValue());
		out.print(sep);
		out.print(StatisticHelper.getNxValue(lenArr, 70).intValue());
		out.print(sep);
		out.print(StatisticHelper.getNxValue(lenArr, 60).intValue());
		out.print(sep);
		out.print(StatisticHelper.getN50(lenArr).intValue());
		out.print(sep);
		out.print(StatisticHelper.getNxValue(lenArr, 40).intValue());
		out.print(sep);
		out.print(StatisticHelper.getNxValue(lenArr, 30).intValue());
		out.print(sep);
		out.print(StatisticHelper.getNxValue(lenArr, 25).intValue());
		out.print(sep);
		out.print(StatisticHelper.getNxValue(lenArr, 20).intValue());
		out.print(sep);
		out.print(StatisticHelper.getNxValue(lenArr, 10).intValue());
		out.print(sep);
		out.print(stats.getMinLength());
		out.print(sep);
		out.print(stats.getMaxLength());
		out.print(sep);
		out.print(stats.getTotalLength());
		out.print(sep);
		out.print(stats.getNContigs());
		out.print(sep);
		out.print(nCnt);
		out.print(sep);
		out.print(plainCnt.get());
		out.print(sep);
		out.print(plainSum.get());
		out.print(sep);
		out.print(cnt.get());
		out.print(sep);
		out.print(fi.getFile());
		out.println(sep);
	}

	private void printHeader() {
		PrintStream out = getOutput();
		out.print("n90");
		out.print(sep);
		out.print("n80");
		out.print(sep);
		out.print("n75");
		out.print(sep);
		out.print("n70");
		out.print(sep);
		out.print("n60");
		out.print(sep);
		out.print("n50");
		out.print(sep);
		out.print("n40");
		out.print(sep);
		out.print("n30");
		out.print(sep);
		out.print("n25");
		out.print(sep);
		out.print("n20");
		out.print(sep);
		out.print("n10");
		out.print(sep);
		out.print("min");
		out.print(sep);
		out.print("max");
		out.print(sep);
		out.print("total");
		out.print(sep);
		out.print("# Contigs");
		out.print(sep);
		out.print("n > n50");
		out.print(sep);
		out.print("n Plain");
		out.print(sep);
		out.print("x Plain bp");
		out.print(sep);
		out.print("Ns");
		out.print(sep);
		out.print("File");
		out.print(sep);
		out.println();
	}

	
	private PrintStream getOutput(){
		if(null == outFile){
			return System.out;
		} else{
			if(null == output) {
				try {
					output = new FileIO(outFile).getPrintStream();
				} catch (IOException e) {
					throw new CurtainUncheckedException("Problems writing to " + outFile);
				}
			}
			return output;
		}
	}

	private List<Integer> getSortedLengthArr(MyProcessor statProc) {
		List<Integer> lenArr = new ArrayList<Integer>();
		for(Entry<Integer,List<Integer>> entry : statProc.getSP().getCoverage2ScoreMap().entrySet()){
			for(Integer len : entry.getValue()){
				lenArr.add(len);
			}
		}
		Collections.sort(lenArr);
		return lenArr;
	}

	@Override
	public void close() throws IOException {
		PrintStream out = getOutput();
		out.close();
		this.output = null;
	}

	public static class MyProcessor extends SequenceStatisticsProcessor {

		public MyProcessor(FileInfo statsFile) {
			super(statsFile);
		}

		public StatisticsProcessor getSP() {
			return getStatsProc();
		}

		@Override
		protected StatisticsProcessor getStatsProc() {
			return super.getStatsProc();
		}

	}


}
